﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Debug = System.Diagnostics.Debug;
using weka.classifiers;
using weka.core;
using weka.filters.unsupervised.attribute;
using Attribute = weka.core.Attribute;
using System.Diagnostics.Contracts;
using weka.filters;

namespace OMC.Classification
{
    /// <summary>
    /// Class <c>TextClassifier</c> classifies a text. Use class <c>TextClassifierBuilder</c> to create instances of this class.
    /// 
    /// <example>
    /// <code>
    /// TextClassifierBuilder classifierBuilder = new TextClassifierBuilder();
    /// // ... configure classifierBuilder
    /// TextClassifier classifier = classifierBuilder.getClassifier();
    /// foreach (TextClassifierResultItem item in classifier.Classify("A text to classify.")) {
    ///     item.ToString();
    /// }
    /// </code>
    /// prints something like
    /// <code>
    /// Class &lt; name of class 1 &gt; with probability &lt; 0.89 &gt;.
    /// Class &lt; name of class 2 &gt; with probability &lt; 0.67 &gt;.
    /// Class &lt; name of class 2 &gt; with probability &lt; 0.99 &gt;.
    /// </code>
    /// The list of result items is not ordered.
    /// </example>
    /// </summary>
    /// <seealso cref="TextClassifierBuilder"/>
    [Serializable()]
    public class TextClassifier
    {
        private Classifier updateableClassifier;
        private Instances filteredInstancesTemplate;
        private Filter filter;
        private Instances unfilteredInstancesTemplate;
        private Attribute textAttribute;


        internal TextClassifier(Classifier updateableClassifier, Filter filter, Instances unfilteredInstancesTemplate, Instances filteredInstancesTemplate)
        {
            Contract.Requires(updateableClassifier != null);
            Contract.Requires(filter != null);
            Contract.Requires(unfilteredInstancesTemplate != null);
            Contract.Requires(unfilteredInstancesTemplate.numInstances() == 0, "Parameter unfilteredInstancesTemplate is only used as template.");
            Contract.Requires(filteredInstancesTemplate != null);
            Contract.Requires(filteredInstancesTemplate.numInstances() == 0, "Parameter filteredInstancesTemplate is only used as template.");
            Contract.Requires(updateableClassifier is UpdateableClassifier);

            this.updateableClassifier = updateableClassifier;
            this.filter = filter;
            this.unfilteredInstancesTemplate = unfilteredInstancesTemplate;
            this.filteredInstancesTemplate = filteredInstancesTemplate;

            textAttribute = unfilteredInstancesTemplate.attribute("text");
        }

        /// <summary>
        /// Classify a text.
        /// </summary>
        /// <param name="text">The text to classify.</param>
        /// <returns></returns>
        public IEnumerable<TextClassifierResultItem> Classify(string text)
        {
            Contract.Requires(text != null);

            Instance unfilteredInstance = new Instance(2);
            unfilteredInstance.setValue(textAttribute, text);
            unfilteredInstance.setDataset(unfilteredInstancesTemplate);

            filter.input(unfilteredInstance);
            Instance filteredInstance = filter.output();

            double[] probabilities = updateableClassifier.distributionForInstance(filteredInstance);

            IList<TextClassifierResultItem> resultItems = new List<TextClassifierResultItem>();
            for (int index = 0; index < probabilities.Length; index++)
            {
                double probability = probabilities[index];
                String clazz = filteredInstancesTemplate.classAttribute().value(index);
                resultItems.Add(new TextClassifierResultItem(clazz, probability));
            }
            return resultItems;
        }

        /// <summary>
        /// Classify a text and return the sorted 10 most probable results
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        public IEnumerable<TextClassifierResultItem> ClassifyTop(string text)
        {
            return Classify(text).OrderByDescending(i => i.Probability).Take(10);
        }

        /// <summary>
        /// Update the classifier using the given instance.
        /// </summary>
        /// <param name="instance"></param>
        public void updateClassifier(Instance instance)
        {
            ((UpdateableClassifier)updateableClassifier).updateClassifier(instance);
        }

        /// <summary>
        /// Save the trained classifier to a file
        /// </summary>
        /// <param name="path"></param>
        public void saveClassifier(String path)
        {
            weka.core.SerializationHelper.write(path, updateableClassifier);
        }
   }
}
